OpenGL ES - 画板案例实现

思维导图

image.png

初始化View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
-(id)initWithCoder:(NSCoder *)aDecoder
{
if ((self = [super initWithCoder:aDecoder])) {

//初始化CAEAGLLayer
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;

//设置透明度
eaglLayer.opaque = YES;

//设置eaglLayer描述属性
/*
1.kEAGLDrawablePropertyRetainedBacking
表示绘图表面显示后,是否保留其内容,通过一个NSNumber 包装一个bool值。如果是NO,表示
显示内容后,不能依赖于相同的内容;如果是YES,表示显示内容后不变,一般只有在需要内容保存不变的情况下才使用YES,设置为YES,会导致性能降低,内存使用量降低。一般设置为NO。

2.kEAGLDrawablePropertyColorFormat
表示绘制表面的内部颜色缓存区格式
*/
eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];

//初始化上下文
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

//判断是否开辟成功以及设置到当前的Context
if (!context || ![EAGLContext setCurrentContext:context]) {
return nil;
}

//设置视图的比例因子
/*
比例因子决定视图中的内容如何从逻辑坐标空间(以点测量)映射到设备坐标空间(以像素为单位)。此值通常为1或2。更高比例的因素表明视图中的每一个点由底层的多个像素表示。例如,如果缩放因子为2,并且视图框大小为50×50点,则用于显示内容的位图的大小为100×100像素。
*/
self.contentScaleFactor = [[UIScreen mainScreen] scale];

//是否需要清屏,默认等于YES
needsErase = YES;
}
return self;
}

layoutSubviews

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-(void)layoutSubviews
{

[EAGLContext setCurrentContext:context];

//判断是否初始化
if (!initialized) {
//如果没有初始化则对OpenGL初始化
initialized = [self initGL];
}
else {
//如果已经初始化则调整layer
[self resizeFromLayer:(CAEAGLLayer*)self.layer];
}

// 清除帧第一次分配
if (needsErase) {
[self erase];
needsErase = NO;
}

}

若没有进行过初始化

initialized 绘制”加油!”

initGL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
-(BOOL)initGL
{

//生成标识一个帧缓存对象和颜色渲染
glGenFramebuffers(1, &viewFrameBuffer);
glGenRenderbuffers(1, &viewRenderBuffer);

//绑定viewFrameBuffer 和 viewRenderBuffer
glBindFramebuffer(GL_FRAMEBUFFER, viewFrameBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);

//绑定一个Drawable对象存储到一个OpenGL ES渲染缓存对象。
/*
创建一个渲染,可以呈现到屏幕上,你将渲染然后分配共享存储通过调用此方法。这个方法的调用替换通常给glrenderbufferstorage。缓存的存储分配了这个方法以后可以显示一个回调presentrenderbuffer:
为绘制缓冲区分配存储区,此处将CAEAGLLayer的绘制存储区作为绘制缓冲区的存储区
参数1:OpenGL ES的结合点为当前绑定的渲染。这个参数的值必须gl_renderbuffer(或gl_renderbuffer_oes在OpenGL ES 1.1语境)
参数2:对象管理数据存储区中的渲染。在iOS中,这个参数的值必须是一个CAEAGLLayer对象

*/
[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id<EAGLDrawable>)self.layer];

//将viewRenderBuffer 绑定到GL_COLOR_ATTACHMENT0
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, viewRenderBuffer);

//获取绘制缓存区的像素宽度 --将绘制缓存区像素宽度存储在backingWidth
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
//获取绘制缓存区的像素高度--将绘制缓存区像素高度存储在backingHeight
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);

//检查GL_FRAMEBUFFER缓存区状态
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
NSLog(@"Make complete framebuffer Object failed! %x",glCheckFramebufferStatus(GL_FRAMEBUFFER));
return NO;
}

//设置视口
glViewport(0, 0, backingWidth, backingHeight);

//创建顶点缓冲对象来保存我们的数据
glGenBuffers(1, &vboId);

//加载画笔纹理
brushTexture = [self textureFromName:@"Particle.png"];

//加载shade
[self setupShaders];


//点模糊效果通过开启混合模式,并设置混合函数
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

//回放录制的路径,这是“加油!”
NSString *path = [[NSBundle mainBundle]pathForResource:@"abc" ofType:@"string"];
//将path 使用NSUTF8StringEncoding 编码
NSString *str = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];

//开辟数组空间-可变的
CCArr = [NSMutableArray array];

//根据abc.string文件,将绘制点的数据,json解析到数组
NSArray *jsonArr = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil];

//遍历jsonArr数组,将数据转为CCPoint类型数据
for (NSDictionary *dict in jsonArr) {
CCPoint *point = [CCPoint new];
point.mX = [dict objectForKey:@"mX"];
point.mY = [dict objectForKey:@"mY"];

//将CCPoint 对象添加到CCArr数组
[CCArr addObject:point];
}

//调用绘制方法:绘制abc.string 绘制的加油字样,延时5秒绘制!
[self performSelector:@selector(paint) withObject:nil afterDelay:0.5];


return YES;

}
手写着色程序 vsh&fsh
  • vsh : 顶点着色器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    //顶点
    attribute vec4 inVertex;

    //矩阵
    uniform mat4 MVP;

    //点的大小
    uniform float pointSize;

    //点的颜色
    uniform lowp vec4 vertexColor;

    //输出颜色
    varying lowp vec4 color;

    void main()
    {
    //顶点计算 = 矩阵 * 顶点
    gl_Position = MVP * inVertex;

    //修改顶点大小
    gl_PointSize = pointSize;
    // 1 * 3.0;

    //将通过uniform 传递进来的颜色,从顶点着色器程序传递到片元着色器
    color = vertexColor;
    }
  • fsh : 片元着色器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //获取纹理
    uniform sampler2D texture;
    /*
    sampler2D,中的2D,表示这是一个2D纹理。我们也可以使用1D\3D或者其他类型的采样器。我们总是
    把这个值设置为0。来指示纹理单元0.
    */

    //获取从顶点程序传递过来的颜色
    //lowp,精度
    varying lowp vec4 color;

    void main()
    {
    //将颜色和纹理组合 是相乘!!!!
    gl_FragColor = color * texture2D(texture, gl_PointCoord);
    }
加载shader

即加载vsh和fsh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
- (void)setupShaders
{

for (int i = 0; i < NUM_PROGRAMS; i++) {

//读取顶点着色程序
char *vsrc = readFile(pathForResource(program[i].vert));
char *fsrc = readFile(pathForResource(program[i].frag));

//将char->NSString 对象
NSString *vsrcStr = [[NSString alloc]initWithBytes:vsrc length:strlen(vsrc)-1 encoding:NSUTF8StringEncoding];
NSString *fsrcStr = [[NSString alloc]initWithBytes:fsrc length:strlen(fsrc)-1 encoding:NSUTF8StringEncoding];

//打印着色程序中的代码
NSLog(@"vsrc:%@",vsrcStr);
NSLog(@"fsrc:%@",fsrcStr);


//attribute
GLsizei attribCt = 0;
//创建字符串数组【1】
GLchar *attribUsed[NUM_ATTRIBS];
//
GLint attrib[NUM_ATTRIBS];

//attribute 变量名称-inVertex(point.vsh)
GLchar *attribName[NUM_ATTRIBS] = {
"inVertex",
};

//uniform变量名称 "MVP", "pointSize", "vertexColor", "texture",
const GLchar *uniformName[NUM_UNIFORMS] = {
"MVP", "pointSize", "vertexColor", "texture",
};

//遍历attribute
for (int j = 0; j < NUM_ATTRIBS; j++)
{
//strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL。
//判断,attribute 变量,是否存在顶点着色器程序中。point.vsh
if (strstr(vsrc, attribName[j]))
{
//attribute个数
attrib[attribCt] = j;
//使用的attribute的名称
attribUsed[attribCt++] = attribName[j];
}
}

//利用shaderUtil.c封装好的方法对programe 进行创建、链接、生成Programe
/*
参数1:vsrc,顶点着色器程序
参数2:fsrc,片元着色器程序
参数3:attribute变量个数
参数4:attribute变量名称
参数5:当前attribute位置
参数6:uniform名字
参数7:program的uniform地址
参数8:program程序地址
*/
glueCreateProgram(vsrc, fsrc,
attribCt, (const GLchar **)&attribUsed[0], attrib,
NUM_UNIFORMS, &uniformName[0], program[i].uniform,
&program[i].id);

//释放vsrc,fsrc指针
free(vsrc);
free(fsrc);

// 设置常数、初始化Uniform
//当前的i == 0
if (i == PROGRAM_POINT)
{
//使用proram program[0].id 等价,以往课程例子中的GLuint program;
glUseProgram(program[PROGRAM_POINT].id);


//为当前程序对象指定uniform变量值
/*
为当前程序对象指定uniform变量MVP赋值

void glUniform1f(GLint location, GLfloat v0);
参数1:location,指明要更改的uniform变量的位置 MVP
参数2:v0,指明在指定的uniform变量中要使用的新值

program[0].uniform[3] = 0
等价于,vsh顶点着色器程序中的uniform变量,MVP = 0;
其实简单理解就是做了一次初始化,清空这个mat4矩阵
*/
glUniform1i(program[PROGRAM_POINT].uniform[UNIFORM_TEXTURE], 0);

// 投影矩阵
/*
投影分为正射投影和透视投影,我们可以通过它来设置投影矩阵来设置视域,在OpenGL中,默认的投影矩阵是一个立方体,即x y z 分别是-1.0~1.0的距离,如果超出该区域,将不会被显示

正射投影(orthographic projection):GLKMatrix4MakeOrtho(float left, float righ, float bottom, float top, float nearZ, float farZ),该函数返回一个正射投影的矩阵,它定义了一个由 left、right、bottom、top、near、far 所界定的一个矩形视域。此时,视点与每个位置之间的距离对于投影将毫无影响。

透视投影(perspective projection):GLKMatrix4MakeFrustum(float left, float right,float bottom, float top, float nearZ, float farZ),该函数返回一个透视投影的矩阵,它定义了一个由 left、right、bottom、top、near、far 所界定的一个平截头体(椎体切去顶端之后的形状)视域。此时,视点与每个位置之间的距离越远,对象越小。

在平面上绘制,只需要使正投影就可以了!!
*/
GLKMatrix4 projectionMatrix = GLKMatrix4MakeOrtho(0, backingWidth, 0, backingHeight, -1, 1);

//模型矩阵,比如你要平移、旋转、缩放,就可以设置在模型矩阵上
//这里不需要这些变换,则使用单元矩阵即可,相当于1 * ? = ?
GLKMatrix4 modelViewMatrix = GLKMatrix4Identity;

//矩阵相乘,就2个矩阵的结果交给MVPMatrix
GLKMatrix4 MVPMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);
/*
void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
功能:为当前程序对象指定uniform变量值
参数1:location 指明要更改的uniform变量的位置 MVP
参数2:count 指定将要被修改的矩阵的数量
参数3:transpose 矩阵的值被载入变量时,是否要对矩阵进行变换,比如转置!
参数4:value ,指向将要用于更新uniform变量MVP的数组指针
*/
glUniformMatrix4fv(program[PROGRAM_POINT].uniform[UNIFORM_MVP], 1, GL_FALSE, MVPMatrix.m);

//点的大小 pointSize
/*
为当前程序对象指定uniform变量pointSize赋值
program[0].uniform[pointSize] = 纹理宽度/画笔比例
*/
glUniform1f(program[PROGRAM_POINT].uniform[UNIFORM_POINT_SIZE], brushTexture.width / kBrushScale);


//笔刷颜色
/*
为当前程序对象指定uniform变量vertexColor赋值
program[0].uniform[vertexColor] = 画笔颜色

void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
功能:为当前程序对象指定uniform变量值
参数1:location 指明要更改的uniform变量的位置 vertexColor
参数2:count 指定将要被修改的4分量的数量
参数3:value ,指向将要用于更新uniform变量vertexColor的值

*/
glUniform4fv(program[PROGRAM_POINT].uniform[UNIFORM_VERTEX_COLOR], 1, brushColor);

}
}

glError();

}

加载画笔纹理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// 创建一个纹理图片
- (textureInfo_t)textureFromName:(NSString *)name
{
CGImageRef brushImage;
CGContextRef brushContext;
GLubyte *brushData;
size_t width,height;
GLuint texId;
textureInfo_t texture;

//首先建立在图像文件的数据一个UIImage对象,然后提取核心图形图像
brushImage = [UIImage imageNamed:name].CGImage;

//获取图片的宽和高
width = CGImageGetWidth(brushImage);
height = CGImageGetHeight(brushImage);

//分配位图上下文所需的内存
brushData = (GLubyte *)calloc(width * height * 4, sizeof(GLubyte));

//使用Core Graphics框架提供的bitmatp创造功能。
/*
CGContextRef CGBitmapContextCreate(
void * data,
size_t width,
size_t height,
size_t bitsPerComponent,
size_t bytesPerRow,
CGColorSpaceRef cg_nullable space,
uint32_t bitmapInfo);

Quartz创建一个位图绘制环境,也就是位图上下文。
参数1:data,要渲染的绘制内容的地址
参数2:位图的宽
参数3:位图的高
参数4:内存中像素的每个组件的位数,比如32位像素格式和RGB颜色空间。一般设置为8
参数5:位图每一行占有比特数
参数5:颜色空间,通过CGImageGetColorSpace(图片)获取颜色空间
参数6:颜色通道,RGBA = kCGImageAlphaPremultipliedLast

*/
brushContext = CGBitmapContextCreate(brushData, width, height, 8, width * 4, CGImageGetColorSpace(brushImage), kCGImageAlphaPremultipliedLast);

//创建完context之后,可以在context上绘制图片
/*
void CGContextDrawImage(CGContextRef c, CGRect rect,
CGImageRef image);
参数1:位图上下文
参数2:绘制的frame
参数3:绘制的图片
*/
CGContextDrawImage(brushContext, CGRectMake(0.0, 0.0f, (CGFloat)width, (CGFloat)height), brushImage);

//接下来将不需要上下文,因此需要释放它以避免内存泄漏
CGContextRelease(brushContext);

//使用OpenGL ES生成纹理
/*
生成纹理的函数
glGenTextures (GLsizei n, GLuint* textures)
参数1:n,生成纹理个数
参数2:存储纹理索引的第一个元素指针
*/
glGenTextures(1, &texId);

//绑定纹理名称 允许建立一个绑定到目标纹理的有名称的纹理。
glBindTexture(GL_TEXTURE_2D, texId);

//设置纹理参数使用缩小滤波器和线性滤波器(加权平均)--设置纹理属性
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

//指定2D纹理图像,为内存中的图像数据提供一个指针。
/*
功能:生成2D纹理
glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels);
参数1:target,纹理目标,因为你使用的是glTexImage2D函数,所以必须设置为GL_TEXTURE_2D
参数2:level,0,基本图像级别
参数3:internalformat,颜色组件;GL_RGBA,GL_ALPHA,GL_RGBA
参数4:width,纹理图像的宽度
参数5:height,纹理图像的高度
参数6:border,纹理边框的宽度,必须为0
参数7:format,像素数据的颜色格式,可不与internalformat一致,可参考internalformat的值
参数8:type,像素数据类型,GL_UNSIGNED_BYTE
参数9:pixels,内存中指向图像数据的指针

*/
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)width, (int)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, brushData);

//生成纹理之后,即可释放brushData数据
free(brushData);

//补充自己定义的texture结构体中的内容
//纹理
texture.id = texId;
//纹理宽度
texture.width = (int)width;
//纹理高度
texture.height = (int)height;

//返回纹理对象数据
return texture;
}
绘制已存储顶点数据的”加油”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
-(void)paint
{

//从0开始遍历顶点,步长为2
/*
为什么步长等于2?
p1,p2,开始点,结束点!
*/
for (int i = 0; i < CCArr.count - 1; i+= 2) {

//从CCArr数组中读取顶点 cp1,cp2
CCPoint *cp1 = CCArr[i];
CCPoint *cp2 = CCArr[i + 1];

//将CCPoint对象 -> CGPoint对象
CGPoint p1,p2;
p1.x = cp1.mX.floatValue;
p2.x = cp2.mX.floatValue;

p1.y = cp1.mY.floatValue;
p2.y = cp2.mY.floatValue;

//在用户触摸的地方绘制屏幕上的线条
[self renderLineFromPoint:p1 toPoint:p2];


}

}
在两点之间绘制线条
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//在用户触摸的地方绘制屏幕上的线条
-(void)renderLineFromPoint:(CGPoint)start toPoint:(CGPoint)end
{
//顶点缓存区
static GLfloat *vertexBuffer = NULL;

//顶点Max(暂时)
static NSUInteger vertexMax = 64;

//顶点个数
NSUInteger vertexCount = 0,count;


//从点到像素转换
//视图的比例因子
CGFloat scale = self.contentScaleFactor;
//将每个顶点与scale 因子相乘
start.x *= scale;
start.y *= scale;

end.x *= scale;
end.y *= scale;

//开辟数组缓存区
if (vertexBuffer == NULL) {
//开辟顶点地址空间
vertexBuffer = malloc(vertexMax * 2 * sizeof(GLfloat));
}

/*
通过把起点到终点的轨迹分解成若干个点,分别来绘制每个点,从而达到线的效果
ceilf()向上取整。不是四舍五入,而是判断后面有小数,去掉小数部分,整数部分加1.
如:123.456 => 124
123.001 => 124

*/

//向缓冲区添加点,所以每个像素都有绘图点
//求得start 和 end 2点间的距离
float seq = sqrtf((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y));

/*
向上取整,求得距离要产生多少个点?
kBrushPixelStep,画笔像素步长
修改kBrushPixelStep 的值,越大,笔触越细;越小,笔触越粗!
*/
NSInteger pointCount = ceilf(seq / kBrushPixelStep);

//比较pointCount 是不是大于1,如果小于1,则count = 1,否则count = pointCount;
count = MAX(pointCount, 1);

//NSLog(@"Count = %ld",count);

for (int i = 0; i < count; i++) {

//判断如果顶点数 > 设置顶点Max
if (vertexCount == vertexMax) {

//修改vertexMax 2倍增长
vertexMax = 2 *vertexMax;

//增加空间开辟
vertexBuffer = realloc(vertexBuffer, vertexMax * 2 *sizeof(GLfloat));

}

//修改vertexBuffer数组的值
//将start 和 end 距离之间,计算出count个点,并存储在vertexBuffer数组中
//x = start.x + (end.x - start.x) * (i/count);
//y = start.y + (end.y - start.y) * (i/count);
//vertexBuffer[0]->x
vertexBuffer[2 * vertexCount + 0] = start.x + (end.x - start.x) * ((GLfloat)i/(GLfloat)count);
//vertextBuffer[1]->y
vertexBuffer[2 * vertexCount + 1] = start.y + (end.y - start.y) * ((GLfloat)i/(GLfloat)count);

/*
NSLog(@"X:%f",vertexBuffer[2 * vertexCount]);
NSLog(@"Y:%f",vertexBuffer[2 * vertexCount + 1]);
*/

//vertexCount 自增1
vertexCount += 1;

}

//加载数据到vertex Buffer对象中
glBindBuffer(GL_ARRAY_BUFFER, vboId);
//将cpu存储的顶点数据->GPU中 复制顶点数组到缓冲中提供给OpenGL使用
glBufferData(GL_ARRAY_BUFFER, vertexCount * 2 * sizeof(GLfloat), vertexBuffer, GL_DYNAMIC_DRAW);

/*
链接顶点属性
glEnableVertexAttribArray启用指定属性,才可在顶点着色器中访问逐顶点的属性数据
参考课件:二、链接顶点属性
*/
glEnableVertexAttribArray(ATTRIB_VERTEX);

glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, GL_FALSE, 2*sizeof(GLfloat), 0);

//绘制
//使用刚刚创建的program[0].id的program
glUseProgram(program[PROGRAM_POINT].id);
/*
根据顶点绘制图形,
参数1:绘制模型 连接线段,参考视觉班第一节课的课件
参数2:起始点,0
参数3:顶点个数
*/
glDrawArrays(GL_POINTS, 0, (int)vertexCount);

//显示buffer
glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);
[context presentRenderbuffer:GL_RENDERBUFFER];

}

已经进行过初始化

调整图层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//调整图层
- (BOOL)resizeFromLayer:(CAEAGLLayer *)layer
{

//根据当前图层大小分配颜色缓冲区
glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);

//绑定一个Drawable对象存储到一个OpenGL ES渲染缓存对象。
/*
创建一个渲染,可以呈现到屏幕上,你将渲染然后分配共享存储通过调用此方法。这个方法的调用替换通常给glrenderbufferstorage。缓存的存储分配了这个方法以后可以显示一个回调presentrenderbuffer:
- (BOOL)renderbufferStorage:(NSUInteger)target fromDrawable:(id<EAGLDrawable>)drawable;
为绘制缓冲区分配存储区,此处将CAEAGLLayer的绘制存储区作为绘制缓冲区的存储区
参数1:OpenGL ES的结合点为当前绑定的渲染。这个参数的值必须gl_renderbuffer(或gl_renderbuffer_oes在OpenGL ES 1.1语境)
参数2:对象管理数据存储区中的渲染。在iOS中,这个参数的值必须是一个CAEAGLLayer对象

*/

[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];

////获取绘制缓存区的像素宽度 --将绘制缓存区像素宽度存储在backingWidth
glGetRenderbufferParameteriv(GL_RENDERBUFFER,GL_RENDERBUFFER_WIDTH, &backingWidth);

////获取绘制缓存区的像素高度--将绘制缓存区像素高度存储在backingHeight
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);

//检查GL_FRAMEBUFFER缓存区状态
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {

NSLog(@"Make compelete framebuffer object failed!%x",glCheckFramebufferStatus(GL_FRAMEBUFFER));
return NO;
}

//更新投影矩阵、模型视图矩阵
// 投影矩阵
/*
投影分为正射投影和透视投影,我们可以通过它来设置投影矩阵来设置视域,在OpenGL中,默认的投影矩阵是一个立方体,即x y z 分别是-1.0~1.0的距离,如果超出该区域,将不会被显示

正射投影(orthographic projection):GLKMatrix4MakeOrtho(float left, float righ, float bottom, float top, float nearZ, float farZ),该函数返回一个正射投影的矩阵,它定义了一个由 left、right、bottom、top、near、far 所界定的一个矩形视域。此时,视点与每个位置之间的距离对于投影将毫无影响。

透视投影(perspective projection):GLKMatrix4MakeFrustum(float left, float right,float bottom, float top, float nearZ, float farZ),该函数返回一个透视投影的矩阵,它定义了一个由 left、right、bottom、top、near、far 所界定的一个平截头体(椎体切去顶端之后的形状)视域。此时,视点与每个位置之间的距离越远,对象越小。

在平面上绘制,只需要使正投影就可以了!!
*/
GLKMatrix4 projectionMatrix = GLKMatrix4MakeOrtho(0, backingWidth, 0, backingHeight, -1, 1);

// //模型矩阵,比如你要平移、旋转、缩放,就可以设置在模型矩阵上
//这里不需要这些变换,则使用单元矩阵即可,相当于1 * ? = ?
GLKMatrix4 modelViewMatrix = GLKMatrix4Identity;

//矩阵相乘,就2个矩阵的结果交给MVPMatrix
GLKMatrix4 MVPMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);


/*
void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
功能:为当前程序对象指定uniform变量值
参数1:location 指明要更改的uniform变量的位置 MVP
参数2:count 指定将要被修改的矩阵的数量
参数3:transpose 矩阵的值被载入变量时,是否要对矩阵进行变换,比如转置!
参数4:value ,指向将要用于更新uniform变量MVP的数组指针
*/
glUniformMatrix4fv(program[PROGRAM_POINT].uniform[UNIFORM_MVP], 1, GL_FALSE, MVPMatrix.m);

//更新视口
glViewport(0, 0, backingWidth, backingHeight);

return YES;

}

修改画笔颜色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)setBrushColorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue
{

//更新画笔颜色 颜色 * 透明度
brushColor[0] = red * kBrushOpacity;
brushColor[1] = green * kBrushOpacity;
brushColor[2] = green * kBrushOpacity;
brushColor[3] = kBrushOpacity;

NSLog(@"%f,%f,%f,%f",brushColor[0],brushColor[1],brushColor[2],brushColor[3]);
NSLog(@"%f,%f,%f",red,green,blue);

//释放初始化
if (initialized) {

//使用program[0].id
glUseProgram(program[PROGRAM_POINT].id);
//将颜色值brushColor 传递到 vertexColor中
glUniform4fv(program[PROGRAM_POINT].uniform[UNIFORM_VERTEX_COLOR], 1, brushColor);

}

}

清理屏幕

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//清空屏幕
-(void)erase
{
//clear frameBuffer
glBindFramebuffer(GL_FRAMEBUFFER, viewFrameBuffer);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

//显示缓存区
glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);
[context presentRenderbuffer:GL_RENDERBUFFER];


}

开始绘制

绘制任意图形,线条

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#pragma mark -- Touch Click
//点击屏幕开始
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//获取绘制的bounds
CGRect bounds = [self bounds];
//获取当前的点击touch
UITouch* touch = [[event touchesForView:self] anyObject];
//设置为firstTouch -> yes
firstTouch = YES;

//获取当前点击的位置信息,x,y
_location = [touch locationInView:self];

//y = height - y
_location.y = bounds.size.height - _location.y;

}

-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CGRect bounds = [self bounds];
UITouch *touch = [[event touchesForView:self]anyObject];

//第一次点击
if (firstTouch) {
//将firstTouch状态改为NO
firstTouch = NO;
//_previousLocation = 获取上一个顶点
_previousLocation = [touch previousLocationInView:self];
_previousLocation.y = bounds.size.height - _previousLocation.y;

}else
{
_location = [touch locationInView:self];
_location.y = bounds.size.height - _location.y;
_previousLocation = [touch previousLocationInView:self];
_previousLocation.y = bounds.size.height - _previousLocation.y;
}

//获取_previousLocation 和 _location 2个顶点,绘制成线条
[self renderLineFromPoint:_previousLocation toPoint:_location];
}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{

CGRect bounds = [self bounds];
UITouch *touch = [[event touchesForView:self]anyObject];

//判断是否为第一次触碰
if (firstTouch) {
firstTouch = NO;
_previousLocation = [touch previousLocationInView:self];
_previousLocation.y = bounds.size.height - _previousLocation.y;
[self renderLineFromPoint:_previousLocation toPoint:_location];
}
}

-(void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSLog(@"Touch Cancelled");

}

-(BOOL)canBecomeFirstResponder
{
return YES;
}

效果

初始化

image.png

谢谢你赏我糖果吃